use std::thread;
use std::sync::{mpsc,Arc, Mutex};


pub struct ThreadPool{
    workers:Vec<Worker>,
    sender:mpsc::Sender<Message>
}

trait FnBox{
    fn call_box(self:Box<Self>);
}

impl <F:FnOnce()> FnBox for F{
    fn call_box(self:Box<Self>){
        (*self)()
    }
}

type Job = Box<FnBox + Send + 'static>;

//#[derive(Debug)]
enum Message{
    NewJob(Job),
    ShutDown
}

type ReceiverType = Arc<Mutex<mpsc::Receiver<Message>>>;

struct Worker{
    id: usize,
    thread:Option<thread::JoinHandle<()>>,
}
impl Worker{
    fn new(id:usize, receiver:ReceiverType) -> Worker{
        Worker{
            id,
            thread:Some(thread::spawn(move ||{
                loop {
                    let message = receiver.lock().unwrap().recv().unwrap();
                    match message {
                        Message::NewJob(job) =>{
                            println!("starting a new job on thread{}", id);
                            job.call_box()
                        },
                        Message::ShutDown => {
                            println!("shutdown ... thread{}", id);
                            break;
                        }
                    }
                }
            }))
        }
    }
}

impl ThreadPool{
    /// Create a new ThreadPool.
    ///
    /// The size is the number of threads in the pool.
    ///
    /// # Panics
    ///
    /// The `new` function will panic if the size is zero.
    pub fn new(size:usize)-> Self{
        assert!(size > 0);
        let mut v:Vec<Worker> = Vec::with_capacity(size);
        let (sender, receiver) = mpsc::channel();
        let receiver = Arc::new(Mutex::new(receiver));
        for id in 0..size{
            v.push(Worker::new(id, receiver.clone()))
        };
        ThreadPool{
            workers:v,
            sender
        }
    }

    pub fn execute<F>(&self, f:F)
        where
            F:FnOnce() + Send + 'static{
            let job = Box::new(f);
            self.sender.send(Message::NewJob(job)).unwrap();
    }
}


impl Drop for ThreadPool{
    fn drop(&mut self){
        println!("Sending terminate message to all workers.");

        for _ in &mut self.workers{
            self.sender.send(Message::ShutDown).unwrap();
        }
        println!("Shutting down all workers.");
        for w in &mut self.workers{
            println!("Shutting down worker {}", w.id);
            if let Some(thread) = w.thread.take(){

                thread.join().unwrap();
            }
        }
    }
}